package com.dogegg.lbbopenapi.aspect;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import com.dogegg.lbbopenapi.OpenapiRegisteredClient;
import com.dogegg.lbbopenapi.OpenapiRegisteredClientService;
import com.dogegg.lbbopenapi.exception.ClientServiceException;
import com.dogegg.lbbopenapi.exception.ErrorCodeEnum;
import com.dogegg.lbbopenapi.exception.OpenApiServiceException;
import com.dogegg.lbbopenapi.request.OpenRequest;
import com.dogegg.lbbopenapi.security.Encrypter;
import com.dogegg.lbbopenapi.security.Signature;
import com.dogegg.lbbopenapi.utils.ValidatorUtil;

import jakarta.validation.ValidationException;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;



@Component
@Aspect
@Order(1)
public class OpenApiAspect {

    private static final Logger log = LoggerFactory.getLogger(OpenApiAspect.class.getName());

    @Autowired
    private  OpenapiRegisteredClientService clientService;
 
    @Pointcut("( @annotation(com.dogegg.lbbopenapi.aspect.OpenApi)" +
            "|| @within(com.dogegg.lbbopenapi.aspect.OpenApi) )")
    public void Pointcut(){}

    @Before("Pointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        try {

            //提取OpenRequest
            OpenRequest request = extractOpenRequest(joinPoint);

            //通用验证字段
            ValidatorUtil.validate(request);

            //请求自定义验证
            request.validate();

            //加载客户端配置
            OpenapiRegisteredClient client = clientService.loadByClientId(request.getClientId());

            //获取请求scope scope 控制权限
            String scope = resloveOpenApiScope(joinPoint);

            List<String> scopes = Arrays.asList(StringUtils.split(client.getScopes(), ","));
            
            if(!scopes.contains(scope)){
                throw OpenApiServiceException.errorCode(ErrorCodeEnum.INVALID_CLIENT_GRANT);
            }

            //验证签名
            verifySign(request,client);

            //解密请求参数敏感数据
            decodeEncryptField(request,client);
            
            //设置client 方便后续使用
            request.setClient(client);

        }catch (ClientServiceException e){
            //客户端信息不存在
            throw OpenApiServiceException.errorCode(ErrorCodeEnum.INVALID_CLIENT_ID);
        } catch (ValidationException e){
            //参数验证失败
            throw new OpenApiServiceException(e.getMessage(),ErrorCodeEnum.PARAM_ERROR.getCode());
        }catch (OpenApiServiceException e){
            //OpenApiServiceException 异常
            throw e;
        }catch (Exception e){
            log.error("OpenApiAspect fail {}",e.getMessage());
            throw OpenApiServiceException.errorCode(ErrorCodeEnum.SERVER_ERROR);
        }

    }

    private OpenRequest extractOpenRequest(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        for(Object obj:args){
            if(obj instanceof OpenRequest){
                return (OpenRequest)obj;
            }
        }

        throw new IllegalArgumentException("parameter must instanceof OpenRequest");
    
    }

    private String resloveOpenApiScope(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        Class<?> declaringClass = method.getDeclaringClass();
        OpenApi annotation = AnnotationUtils.findAnnotation(method, OpenApi.class);
        if(annotation == null){
            annotation = AnnotationUtils.findAnnotation(declaringClass, OpenApi.class);
        }
        return annotation.value();
    }

    private void decodeEncryptField(OpenRequest request,OpenapiRegisteredClient client) {
        String key = client.getClientSecret();
        final List<Field> fields = new ArrayList<>();
        ReflectionUtils.doWithFields(request.getClass(), field -> {
            if (String.class.isAssignableFrom(field.getType()) && field.isAnnotationPresent(Encrypt.class)) {
                fields.add(field);
            }
        });
        try {
            for(Field field:fields){
                field.setAccessible(true);
                field.set(request,  Encrypter.decrypt(client.getEncryptTypes(),((String) field.get(request)),key));
            }
        }catch (Exception e){
            log.error("decodeEncryptField fail",e.getMessage());
            throw OpenApiServiceException.errorCode(ErrorCodeEnum.PARAM_DECRYPT_ERROR);
        }
    }

    public void verifySign(OpenRequest request,OpenapiRegisteredClient client){
        try {
            String sign = request.getSignature();
            Map<String, Object> signMap = request.getSignMap();
            if(Signature.verifySign(client.getSignTypes(), signMap,sign, client.getPubKey())){
                return;
            }
        }catch (Exception e){
            log.error("verifySign fail {}",e.getMessage());
        }
        throw OpenApiServiceException.errorCode(ErrorCodeEnum.PARAM_SIGN_ERROR);
    }
}
